package com.wang.cloud.common.auth.util;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.wang.cloud.common.auth.enums.LoginDeviceEnum;
import com.wang.cloud.common.auth.enums.LoginTypeEnum;
import com.wang.cloud.common.core.domain.model.LoginUser;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * StpLogic 门面类，管理项目中所有的 StpLogic 账号体系
 * （项目中有多少套账号表，就声明几个 StpLogic 会话对象）
 *
 * @author wang ye
 * @date 2024/5/27
 */
public class StpKit {

    private static final String LOGIN_TYPE_KEY = "loginType";
    /**
     * 登录用户信息缓存的key
     */
    public static final String LOGIN_USER_KEY = "loginUser";
    public static final String USER_KEY = "userid";

    /**
     * 普通用户端 会话对象，管理 普通用户端 所有账号的登录、权限认证
     */
    public static final StpLogic APP_USER = new StpLogicJwtForSimple(LoginTypeEnum.APP.getLoginType()) {
        @Override
        public String splicingKeyTokenName() {
            return LoginTypeEnum.APP.getTokenName();
        }
    };

    /**
     * 系统平台管理端 会话对象，管理 系统平台管理端 所有账号的登录、权限认证
     */
    public static final StpLogic SYS_MANAGE = new StpLogicJwtForSimple(LoginTypeEnum.SYS_MANAGE.getLoginType()) {
        @Override
        public String splicingKeyTokenName() {
            return LoginTypeEnum.SYS_MANAGE.getTokenName();
        }
    };

    /**
     * 站点运营端 会话对象，管理 站点运营端 所有账号的登录、权限认证
     */
    public static final StpLogic STATION_MANAGE = new StpLogicJwtForSimple(LoginTypeEnum.STATION_MANAGE.getLoginType()) {
        @Override
        public String splicingKeyTokenName() {
            return LoginTypeEnum.STATION_MANAGE.getTokenName();
        }
    };

    /**
     * 机构运营端 会话对象，管理 机构运营端 所有账号的登录、权限认证
     */
    public static final StpLogic ORG_MANAGE = new StpLogicJwtForSimple(LoginTypeEnum.ORG_MANAGE.getLoginType()) {
        @Override
        public String splicingKeyTokenName() {
            return LoginTypeEnum.ORG_MANAGE.getTokenName();
        }
    };


    private static final Collection<StpLogic> STP_LOGIC_LIST;

    static {
        ArrayList<StpLogic> list = CollectionUtil.toList(APP_USER, STATION_MANAGE, ORG_MANAGE, SYS_MANAGE);
        STP_LOGIC_LIST = CollectionUtil.unmodifiable(list);
    }

    public static StpLogic getStpLogic(String loginType) {
        if (ObjectUtil.isEmpty(loginType)) {
            // 未指定账号体系，默认为普通用户端
            return APP_USER;
        }
        for (StpLogic stpLogic : STP_LOGIC_LIST) {
            if (Objects.equals(stpLogic.getLoginType(), loginType)) {
                return stpLogic;
            }
        }
        throw new RuntimeException("不支持的账号体系");
    }

    /**
     * 根据请求头中的登陆类型，确定当前操作的账号体系
     * 并获取对应的Sa-Token 权限认证，逻辑实现类
     *
     * @return
     */
    public static StpLogic getStpLogic() {
        // 从请求头获取LoginType
        String loginType = (String) getStorageIfAbsentSet(LOGIN_TYPE_KEY, () -> SaHolder.getRequest()
                .getHeader(LOGIN_TYPE_KEY));
        return getStpLogic(loginType);
    }

    /**
     * 根据请求头中的登陆类型，确定当前操作的账号体系
     * 并检验当前会话是否已经登录，如未登录，则抛出异常
     */
    public static void checkLogin() {
        getStpLogic().checkLogin();
    }

    /**
     * 获取用户(多级缓存)
     * login id 就是用户id
     */
    public static Long getUserId() {
        return (Long) getStorageIfAbsentSet(USER_KEY, () -> {
            StpLogic stpLogic = getStpLogic();
            Object loginId = stpLogic.getLoginId();
            return NumberUtil.parseLong(loginId.toString());
        });
    }

    /**
     * 获取用户(多级缓存)
     */
    public static LoginUser getLoginUser() {
        return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> {
            StpLogic stpLogic = getStpLogic();
            SaSession session = stpLogic.getSession();
            if (ObjectUtil.isNull(session)) {
                return null;
            }
            return session.get(LOGIN_USER_KEY);
        });
    }

    /**
     * 获取用户
     */
    public static LoginUser getLoginUser(SaTokenInfo tokenInfo) {
        StpLogic stpLogic = getStpLogic(tokenInfo.getLoginType());
        SaSession session = stpLogic.getSessionByLoginId(tokenInfo.getLoginId());
        if (ObjectUtil.isNull(session)) {
            return null;
        }
        return (LoginUser) session.get(LOGIN_USER_KEY);
    }


    public static Object getStorageIfAbsentSet(String key, Supplier<Object> handle) {
        SaStorage storage = SaHolder.getStorage();
        Object value = storage.get(key);
        if (Objects.isNull(value)) {
            value = handle.get();
            if (Objects.nonNull(value)) {
                storage.set(key, value);
            }
        }
        return value;
    }

    /**
     * 执行登录程序
     *
     * @param loginUser   用户信息
     * @param loginType   登录类型
     * @param loginDevice 登录设备 （默认web）
     */
    public static SaTokenInfo doLogin(LoginUser loginUser, String loginType, String loginDevice) {
        return doLogin(loginUser, loginType, loginDevice, null);
    }

    /**
     * 执行登录程序
     *
     * @param loginUser
     * @param loginType
     * @param loginDevice
     * @param model
     * @return
     */
    public static SaTokenInfo doLogin(LoginUser loginUser, String loginType, String loginDevice, SaLoginModel model) {
        LoginTypeEnum.getEnumByLoginType4Check(loginType);
        LoginDeviceEnum deviceEnum = LoginDeviceEnum.getEnumByLoginDevice(loginDevice);
        StpLogic stpLogic = StpKit.getStpLogic(loginType);
        if (Objects.isNull(model)) {
            model = SaLoginConfig.create();
            if (Objects.equals(deviceEnum, LoginDeviceEnum.WX_MA)) {
                // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
                // 例如: 后台用户30分钟过期 app用户1天过期、微信小程序30天过期
                int timeout = 2592000;
                model.setTimeout(timeout);
                model.setActiveTimeout(timeout);
            }
        }
        model.setDevice(deviceEnum.getDevice());
        stpLogic.login(loginUser.getUserId(), model);
        SaSession session = stpLogic.getSession();
        session.updateTimeout(model.getTimeout());
        session.set(LOGIN_USER_KEY, loginUser);
        return stpLogic.getTokenInfo();
    }

    /**
     * 在当前客户端退出登录
     */
    public static void logout() {
        for (StpLogic logic : STP_LOGIC_LIST) {
            logic.logout();
        }
    }

    /**
     * 客户端退出登录
     *
     * @param loginType
     * @param userId
     */
    public static void logout(String loginType, Long userId) {
        StpKit.getStpLogic(loginType)
                .logout(userId);
    }
}
